#include "tcpclient.hpp"

#include <sstream>
#include <string>
#include <unistd.h>

#include "topconn_string.hpp"
#include "topconn_error.hpp"

namespace topconn
{
#define maxBufSize      8192

    TcpClient::TcpClient()
        : m_sockFd(-1)
        , m_port(0)
        , m_maxTimeout(30)
        , m_maxConnTimeout(5)
        , m_length(0)
        , m_isBlock(true)
        , m_isIpv6(false)
        , m_isReconn(false)
        , m_isKeepalive(false)
        , m_lastActiveTime(0)
    {
    }

    TcpClient::TcpClient(const string& addr, uint16_t port)
        : m_sockFd(-1)
        , m_addr(addr)
        , m_port(port)
        , m_maxTimeout(30)
        , m_maxConnTimeout(5)
        , m_length(0)
        , m_isBlock(true)
        , m_isIpv6(false)
        , m_isReconn(false)
        , m_isKeepalive(false)
        , m_lastActiveTime(0)
    {
    }

    TcpClient::TcpClient(const string& addr, uint16_t port, uint16_t timeout)
        : m_sockFd(-1)
        , m_addr(addr)
        , m_port(port)
        , m_maxTimeout(timeout)
        , m_maxConnTimeout(5)
        , m_length(0)
        , m_isBlock(true)
        , m_isIpv6(false)
        , m_isReconn(false)
        , m_isKeepalive(false)
        , m_lastActiveTime(0)
    {
    }

    TcpClient::~TcpClient()
    {
        disConnect();
    }

    void TcpClient::setAddr(const string& addr, uint16_t port)
    {
        m_addr = addr;
        m_port = port;
    }

    void TcpClient::setMaxTimeout(uint16_t maxTimeout)
    {
        m_maxTimeout = maxTimeout;
    }

    void TcpClient::setMaxConnTimeout(uint16_t maxConnTimeout)
    {
        m_maxConnTimeout = maxConnTimeout;
    }

    void TcpClient::setLength(uint16_t length)
    {
        m_length = length;
    }

    void TcpClient::setBlock(bool on)
    {
        m_isBlock = on;
    }

    void TcpClient::setIpv6(bool on)
    {
        m_isIpv6 = on;
    }

    void TcpClient::setReconnect(bool on)
    {
        m_isReconn = on;
    }

    void TcpClient::setKeepalive(bool on)
    {
        m_isKeepalive = on;
    }

    const string& TcpClient::getAddr() const
    {
        return m_addr;
    }

    uint16_t TcpClient::getPort() const
    {
        return m_port;
    }

    uint16_t TcpClient::getMaxTimeout() const
    {
        return m_maxTimeout;
    }

    uint16_t TcpClient::getMaxConnTimeout() const
    {
        return m_maxConnTimeout;
    }

    uint16_t TcpClient::getLength() const
    {
        return m_length;
    }

    bool TcpClient::isBlock() const
    {
        return m_isBlock;
    }

    bool TcpClient::isIpv6() const
    {
        return m_isIpv6;
    }

    bool TcpClient::isReconnect() const
    {
        return m_isReconn;
    }

    bool TcpClient::isKeepalive() const
    {
        return m_isKeepalive;
    }

    int TcpClient::connect()
    {
        int iRet = 0;
        if (m_sockFd > 0)
        {
            if (!SockUtil::isClosed(m_sockFd))
            {
                return SUCESSED;
            }
            disConnect();
        }

        m_sockFd = SockUtil::create(m_isBlock, m_isIpv6);
        if (m_sockFd < 0)
        {
            return CREATE_SOCKET_ERROR;
        }

        if (m_isKeepalive)
        {
            SockUtil::setKeepAlive(m_sockFd, true);
        }

        if ((iRet = SockUtil::connect(m_sockFd, m_addr,m_port,m_isIpv6)) != 0)
        {
            if (errno == EINPROGRESS)
            {
                iRet = SockUtil::isWriteAble(m_sockFd, m_maxConnTimeout);
                if (iRet == 0)
                    return CONNECT_TIMEOUT;
                else if (iRet < 0)
                    return CONNECT_ERROR;
            }
            else
            {
                return CONNECT_ERROR;
            }
        }
        return SUCESSED;
    }

    int TcpClient::disConnect()
    {
        if (m_sockFd > 0)
        {
            close(m_sockFd);
            m_sockFd = -1;
        }
        return 0;
    }

    bool TcpClient::isConnected()
    {
        return m_sockFd < 0 ? false : !SockUtil::isClosed(m_sockFd);
    }

    int TcpClient::sendto(const string& sndbuf)
    {
        int iRet = 0;
        string ssndBuf(sndbuf);
        if ( m_sockFd < 0 )
        {
            return INVALID_SOCKET;
        }

        if(m_isReconn && SockUtil::isClosed(m_sockFd))
        {
            disConnect();
            connect();
        }

        if( m_length > 0 )
        {
            char buf[128] = {0};
            sprintf(buf,"%0*d",m_length,sndbuf.size());
            ssndBuf.insert(0,buf);
        }

        if (SockUtil::send(m_sockFd, ssndBuf) != ssndBuf.length())
        {
            return SEND_ERROR;
        }
        return SUCESSED;
    }

    int TcpClient::recvfrom(string& rcvbuf)
    {
        int iRet = 0;
        if (m_sockFd < 0)
        {
            return INVALID_SOCKET;
        }

        iRet = SockUtil::isReadAble(m_sockFd, m_maxTimeout);
        if (iRet < 0)
            return OTHER_ERROR;
        else if (iRet == 0)
            return RECV_TIMEOUT;

        int option = 0;
        uint32_t nRcvMesgLen = maxBufSize;
        if (m_length > 0)
        {
            string rcvHeader;
            iRet = SockUtil::recv(m_sockFd, rcvHeader, m_length);
            if (iRet < 0)
                return RECV_ERROR;
            else if (iRet == 0)
            {
                disConnect();
                return PEER_CLOSE;
            }
            if (!topconn_string::isnumber(rcvHeader))
            {
                return RCVDATA_INVALID;
            }
            nRcvMesgLen = std::stoi(rcvHeader);
        }
        iRet = SockUtil::recv(m_sockFd, rcvbuf, nRcvMesgLen, option);
        if (iRet < 0)
            return RECV_ERROR;
        else if (iRet == 0)
        {
            disConnect();
            return PEER_CLOSE;
        }
        updateActiveTime();
        return SUCESSED;
    }

    void TcpClient::updateActiveTime()
    {
        struct timespec t;
        clock_gettime(CLOCK_MONOTONIC, &t);
        m_lastActiveTime = t.tv_sec;
    }
}